/**
 * Queryable 及其子类,
 * 从原始字符串表达式中分析出来具体的 odata 对象
 * 或将 odata 对象转换成字符串表达式
 */
class Queryable {
    constructor(key) {
        this.key = key;
        this.value = undefined;
        this.cached = false;
    }

    get() {
        return `${this.key}=${this.value}`;
    }

    set(raw) {
        this.raw = raw;
        switch (this.key) {
            case '$count':
                this.value = raw === 'true' ? true : false;
                break;
            case '$top':
            case '$skip':
                this.value = Number(raw);
                break;
            default:
                this.value = raw;
                break;
        }
    }
}

class OrderByQueryable extends Queryable {
    constructor(key) {
        super(key);
    }

    get() {
        let result = '';
        if (!this.cached) {
            let value;
            result = this.key + '=';
            if (this.value instanceof Array && this.value.length > 0) {
                for (let i = 0; i < this.value.length; i++) {
                    value = this.value[i];
                    if (i !== 0) {
                        result += ',';
                    }

                    result += `${value.field}${value.ascending ? '' : ' desc'}`;
                }
            }
        }

        return result;
    }

    set(raw) {
        super.set(raw);
        if (typeof this.raw === 'string' && this.raw.length > 0) {
            let stack = this.raw.split(',');
            if (stack instanceof Array && stack.length > 0) {
                let key, value, arr;
                this.value = [];
                for (let i = 0; i < stack.length; i++) {
                    key = stack[i];
                    if (key.includes(' ')) {
                        arr = stack[i].split(' ');
                        if (arr.length > 0) {
                            key = arr[0].trim();
                            if (arr.length > 1) {
                                value = arr[1].trim();
                                value = (value && value === 'desc') ? false : true;
                            }

                            this.value.push({ field: key, ascending: value });
                        }
                    } else {
                        this.value.push({ field: key, ascending: true });
                    }
                }
            }
        }
    }
}

class ExpandQueryable extends Queryable {
    constructor(key) {
        super(key);
    }

    get() {
        let result = '';
        if (!this.cached) {
            let value, index = 0;
            result = this.key + '=';
            for (let key in this.value) {
                value = this.value[key];
                if (index !== 0) {
                    result += ',';
                }

                if (value instanceof OData) {
                    //expand嵌套表达式
                    result += `${key}(${value.get()})`;
                } else {
                    //expand非嵌套表达式
                    result += key;
                }

                index++;
            }
        }

        return result;
    }

    set(raw) {
        super.set(raw);
        if (this.raw.includes('$') && typeof this.raw === 'string' && this.raw.length > 0) {
            this.value = this.stackQuery(this.raw);
            for (const key in this.value) {
                if (this.value.hasOwnProperty(key)) {
                    let element = this.value[key];
                    if (element && element.includes('$')) {
                        let odata = new OData();
                        odata.set(element);
                        element = odata;
                        this.value[key] = element;
                    }

                    if (key.includes('_')) {
                        let arr, cacheKey, field, rename, cacheKeyLower;
                        arr = key.split('_');
                        if (arr.length > 0) {
                            cacheKey = arr[0] || '';
                            cacheKeyLower = cacheKey.toLowerCase();
                        }
                        if (arr.length > 1) {
                            field = arr[1] || '';
                        }
                        if (arr.length > 2) {
                            rename = arr[2];
                        }

                        if (cacheKey && cacheKeyLower && field) {
                            var cache = localCache[cacheKeyLower];
                            if (cache && cache._key === cacheKey) {
                                this.cached = true;
                            }
                        }
                    }
                }
            }
        }
    }

    stackQuery(value, spliter, start, end) {
        if (typeof value === 'string' && value.length > 0) {
            spliter = spliter || /\,/;
            start = start || '(';
            end = end || ')';
            let stack = [], result = {}, last = 0, next = 0, cur = 0, k, v, one, two, three;
            for (let index = 0; index < value.length; index++) {
                const element = value[index];
                if (element === start) {
                    stack.push(index);
                } else if (element === end) {
                    last = stack.pop();
                }

                one = spliter.test(element);
                two = element === end;
                three = index + 1 === value.length;
                if (stack.length === 0 && (one || two || three)) {
                    cur = last === 0 ? 0 : (last + 1);
                    if (one && value[index - 1] !== end) {
                        k = value.substring(last === 0 ? 0 : last, index);
                        v = k;
                    } else if (two) {
                        k = value.substring(next, last);
                        v = value.substring(cur, index);
                    } else if (three) {
                        k = value.substring(cur - 1, index + 1);
                        v = k;
                    }

                    if (k && !spliter.test(k)) {
                        result[k] = v;
                    }

                    last = index;
                    if (one) {
                        next = index + 1;
                        last = index + 1;
                    }
                }
            }

            return result;
        }
    }
}

class OData {
    constructor(url) {
        this.raw = undefined;
        this.cached = false;
        this._count = new Queryable('$count');
        this._filter = new Queryable('$filter');
        this._select = new Queryable('$select');
        this._expand = new ExpandQueryable('$expand');
        this._top = new Queryable('$top');
        this._skip = new Queryable('$skip');
        this._orderby = new OrderByQueryable('$orderby');
    }

    get $count() {
        return this._count;
    }

    get $filter() {
        return this._filter;
    }

    get $select() {
        return this._select;
    }

    get $expand() {
        return this._expand;
    }

    get $top() {
        return this._top;
    }

    get $skip() {
        return this._skip;
    }

    get $orderby() {
        return this._orderby;
    }

    count(exp) {
        this.$count.get(exp);
    }

    get() {
        let result = '', inner = !(this.raw && this.raw.url);
        if (!this.cached) {
            let spliter = inner ? ';' : '&';

            if (this.$count.value) {
                result += (result ? spliter : '') + this.$count.get();
            }

            if (this.$filter.value) {
                result += (result ? spliter : '') + this.$filter.get();
            }

            if (this.$select.value) {
                result += (result ? spliter : '') + this.$select.get();
            }

            if (this.$expand.value) {
                result += (result ? spliter : '') + this.$expand.get();
            }

            if (this.$top.value) {
                result += (result ? spliter : '') + this.$top.get();
            }

            if (this.$skip.value) {
                result += (result ? spliter : '') + this.$skip.get();
            }

            if (this.$orderby.value) {
                result += (result ? spliter : '') + this.$orderby.get();
            }
        }

        return inner ? result : (this.raw.url + '?' + result);
    }

    set(url) {
        if (typeof url === 'string') {
            let spliter = url.includes('&') ? /\&|\?/ : undefined;
            let queryString = this.stackSplit(url, spliter);
            if ($.com.isArray(queryString)) {
                this.raw = {};
                let cur, key, value, index = 0;
                for (let i = 0; i < queryString.length; i++) {
                    cur = queryString[i] || '';
                    if (cur.includes('$')) {
                        index = cur.indexOf('=');
                        if (index > 0) {
                            key = cur.substring(0, index);
                            value = cur.substring(index + 1, cur.length);
                            this.raw[key] = value;
                            this[key].set(value);
                        }
                    } else {
                        this.raw.url = cur;
                    }
                }
            }
        }
    }

    /**
     * 尚不满足 ; 开头的情况
     * @param {*} value 
     * @param {*} spliter 
     * @param {*} start 
     * @param {*} end 
     */
    stackSplit(value, spliter, start, end) {
        if (typeof value === 'string' && value.length > 0) {
            spliter = spliter || /\;/;
            start = start || '(';
            end = end || ')';
            let stack = [], result = [], last = 0, cur = 0, v, one, two;
            for (let index = 0; index < value.length; index++) {
                const element = value[index];
                if (element === start) {
                    stack.push(index);
                } else if (element === end) {
                    stack.pop();
                }

                one = spliter.test(element);
                two = index + 1 === value.length;
                if (stack.length === 0 && (one || two)) {
                    cur = last === 0 ? 0 : (last + 1);
                    cur = (cur === 0 && result.length === 0 && !one && !two) ? 1 : cur;
                    if (one) {
                        last = index;
                        v = value.substring(cur, index);
                        if (v) {
                            result.push(v);
                        }
                    } else if (two) {
                        last = index;
                        v = value.substring(cur, index + 1);
                        if (v && !spliter.test(v)) {
                            result.push(v);
                        }
                    }
                }
            }

            return result;
        }
    }
}